Implement HTTP 103 Early Hints (RFC 8297) interim responses - #2298
Conversation
Add Response.sendEarlyHints() to send 103 Early Hints interim responses over both HTTP/1.1 and HTTP/2. An interim response carries the headers currently set on the response (e.g. Link preload hints), does not commit the response, and may be sent multiple times before the final response. Core (modules/http): - Register HttpStatus.EARLY_HINTS_103. - HttpResponsePacket holds a transient interimStatus, separate from the final status, so serializing an interim response leaves the final response's status untouched. - HttpCodecFilter serializes the interim status line and headers without marking the headers serialized (new encodeMimeHeaders overload), so the same headers are re-emitted with the final response. - OutputBuffer.writeInterimResponse() writes the interim header without committing. HTTP/2 (modules/http2): - DefaultOutputSink emits the interim response as a HEADERS frame (END_HEADERS, no END_STREAM) without committing the stream, reusing the interimStatus signal the same way the existing 100-Continue path spans both protocols. EncoderUtils.encodeUserHeaders gained a markSerialized flag so interim headers survive into the final response (HTTP/2 uses the same per-header serialized flag as HTTP/1.1). - Http2ClientFilter now accepts inbound interim 1xx responses instead of failing with PROTOCOL_ERROR: a 1xx HEADERS block is decoded and delivered as a standalone interim response without advancing the stream's header/trailer state, so the final response is still parsed correctly. This also fixes inbound 100-Continue over HTTP/2. http-server: - Response.sendEarlyHints() sets the interim status and writes it, guarded to HTTP/1.1 and HTTP/2 only. Tests: EarlyHintsTest (HTTP/1.1) and Http2EarlyHintsTest verify the 103 carries the Link header and that the same header is still present on the final response; the full http2 suite and HttpContinueTest remain green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Although Servlet spec 6.2 has not been released yet, how about adding it to Reference) https://github.com/jakartaee/servlet/pull/672/files For example, it is as follows: |
|
I think the AJP Protocol also needs to be modified. It would be good if you referred to the section below and considered it in a similar manner. |
|
Great feedback. Let me take a look. |
|
AJP is going to be more work than anticipated because there is no foundation and it's even not implemented in Tomcat so it cannot be used as reference. |
Mirrors the HTTP/1.1 path: AjpHandlerFilter routes interim packets through a new isInterimResponse() branch (without committing the response), and AjpMessageUtils encodes the interim status code with the currently-set headers, skipping the auto-injection of Content-Type/Content-Length/Content-Language which describe the final response. Tomcat NO-OPs early hints over AJP. We implement instead because the AJP13 protocol does not forbid multiple SEND_HEADERS packets — grizzly already emits multiple for 100-Continue — and forwarding is the upstream connector's concern. Addresses review comment in PR eclipse-ee4j#2298. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the facade method to HttpServletResponseImpl delegating to the underlying Response#sendEarlyHints(). The Servlet 6.2 spec signature does not declare IOException, so the (effectively unreachable) async write path's checked exception is logged at WARNING and swallowed — matches glassfish web-core's ResponseFacade.sendEarlyHints. Bumps jakarta.servlet-api 6.1.0 -> 6.2.0-M2. Addresses review comment in PR eclipse-ee4j#2298. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an encodeInitialLine(HttpPacket, HttpStatus, Buffer, MemoryManager) overload on HttpCodecFilter (default delegates to the single-arg form), overridden in HttpServerFilter to thread the status through a shared private helper. The interim-response branch in HttpCodecFilter now calls into this overload instead of inlining its own status-line encoding, restoring CustomReasonPhrase support along the way (only for the final status — interim always emits the registered reason phrase). Addresses review comment in PR eclipse-ee4j#2298. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deletes the acknowledgment-specific lifecycle from HttpResponsePacket (setAcknowledgement, isAcknowledgement, acknowledged) and routes 100-Continue through the same interimStatus mechanism as 103 Early Hints. The four parallel isAcknowledgement branches in HttpCodecFilter, AjpHandlerFilter, AjpMessageUtils and DefaultOutputSink (HTTP/2) are removed; the interim branch handles both. Side-effects of the old acknowledged() that remained relevant (clearing request.requiresAcknowledgement when the cleared interim was a 100 Continue) move into interimResponseSent. OutputBuffer's two identical methods (acknowledge, writeInterimResponse) merge into the latter. Fixes a latent bug exposed by the unification: HttpServerFilter was calling prepareResponse (which mutates the response headers map with Date/Content-Type/Content-Length) for interim responses too — those mutations would leak into the interim packet on the wire and duplicate on the final response. prepareResponse is now skipped for interim. Also fixes ServletHandler.AckActionImpl.acknowledge() which was setting the final response status to 100 before calling sendAcknowledgement — previously masked by acknowledged() clearing httpStatus, now surfaces because interimResponseSent does not. Regression test added. Addresses review comment in PR eclipse-ee4j#2298. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DecoderUtils#finalizeKnownHeader had case "expect" missing return, falling through into case "connection" which throws PROTOCOL_ERROR — so every HTTP/2 request carrying Expect: 100-continue was rejected at decode time. Same fall-through fixed on case "te". HTTP/2 100-Continue now flows end-to-end via the app-level Response#sendAcknowledgement path and the unified interim mechanism added in the previous commit. HttpServerFilter#prepareRequest now uses Protocol#isAtLeast(HTTP_1_1) for the legacy-protocol check and tightens the chunked-ack branch to HTTP/1.1 specifically. The HTTP/2 case is left to the app-level flow. Addresses review comment in PR eclipse-ee4j#2298. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@carryel: all your feedback has been addressed. PR description is also updated. |
specifically Servlet 6.1
|
Thank you for merging. What's the approximate release date for 5.0.2? |
As far as I know, Grizzly does not release updates regularly. I believe that if there is a need or if a sufficient number of features are gathered, it will be possible to release updates at any time upon request. |
|
We will release Grizzly 5.0.2 soon, there's also an important fix pending |
Add
Response.sendEarlyHints()to send 103 Early Hints interim responses over HTTP/1.1, HTTP/2 and AJP. An interim response carries the headers currently set on the response (e.g.Linkpreload hints), does not commit the response, and may be sent multiple times before the final response. Inspired by Jetty 12 and Tomcat 12.Prerequisite for a green build of eclipse-ee4j/mojarra#5748.
Core (
modules/http)HttpStatus.EARLY_HINTS_103.HttpResponsePacketholds a transientinterimStatusseparate from the final status; serializing an interim response leaves the final status untouched.HttpCodecFilterserializes the interim status line and headers without marking the headers serialized, so they re-emit with the final response.HttpServerFilter.encodeInitialLinerefactored to share a single private helper with a(packet, status)overload — preservesCustomReasonPhraseon the final response only.interimStatusmechanism (removedsetAcknowledgement/isAcknowledgement/acknowledgedfromHttpResponsePacket; the four parallelisAcknowledgementbranches in HTTP/1.1, AJP and HTTP/2 codecs collapse into the interim branch).OutputBuffer.acknowledge()andwriteInterimResponse()collapse into the latter.HttpServerFilterno longer runsprepareResponse(which injectsDate/Content-Type/Content-Length) on interim packets — those mutations belong to the final response.HTTP/2 (
modules/http2)DefaultOutputSinkemits the interim as a HEADERS frame (END_HEADERS, no END_STREAM) without committing the stream.Http2ClientFilteraccepts inbound interim 1xx responses instead of failing withPROTOCOL_ERROR.DecoderUtils.finalizeKnownHeaderhad a missingreturnoncase "expect":(andcase "te":) causing every HTTP/2Expect: 100-continuerequest to be rejected at decode time — fixed. HTTP/2 100-Continue now works end-to-end via the unified interim mechanism.HttpServerFilter#prepareRequestuses the newProtocol#isAtLeast(HTTP_1_1)for the legacy-protocol check.AJP (
modules/http-ajp)AjpHandlerFilterandAjpMessageUtilsencode interim responses asSEND_HEADERSpackets without committing — the existing 100-Continue mechanism generalised. Tomcat NO-OPs early hints over AJP; we implement because the AJP13 protocol does not forbid multipleSEND_HEADERSpackets, and forwarding is the upstream connector's concern.Servlet (
modules/http-servlet)HttpServletResponseImpl.sendEarlyHints()(Servlet 6.2). The Servlet 6.2 spec signature does not declareIOException; the (effectively unreachable) async write path's checked exception is logged and swallowed — matches glassfish web-core'sResponseFacade.ServletHandler.AckActionImpl.acknowledge()no longer leaks status100onto the final response after the unification (regression test added).jakarta.servlet-apibumped 6.1.0 → 6.2.0-M2.Tests
EarlyHintsTest(HTTP/1.1),Http2EarlyHintsTest,AjpEarlyHintsTest— interim 103 carriesLink, final response still has it.Http2ContinueTest— HTTP/2 100-Continue end-to-end.ServletHttpContinueTestregression for the auto-ack leak.HttpContinueTest,BasicAjpTest.test100ContinuePost,ServletHttpContinueTestall still green after unification.